
// <copyright file="PropertyPerfCounterInjector.cs" company="Subhadra Software Solutions">
//   
// </copyright>
// <summary>
//   The property perf counter injector.
// </summary>

namespace SubhadraSolutions.Sharp.Utils.Diagnostics.Performance
{
    using System;
    using System.Reflection;
    using System.Reflection.Emit;

    using SubhadraSolutions.Sharp.Utils.Algorithms;
    using SubhadraSolutions.Sharp.Utils.Contracts;
    using SubhadraSolutions.Sharp.Utils.Reflection;

    /// <summary>
    /// The property perf counter injector.
    /// </summary>
    public class PropertyPerfCounterInjector : AbstractInjectingComponent<PerfCounterCategoryAttribute>
    {
        #region Static Fields

        /// <summary>
        /// The instance.
        /// </summary>
        private static readonly PropertyPerfCounterInjector InstanceObject = new PropertyPerfCounterInjector();

        #endregion Static Fields

        #region Fields

        /// <summary>
        /// The add instance un typed method info.
        /// </summary>
        private readonly MethodInfo _addInstanceUnTypedMethodInfo = typeof(PerfCounterManager).GetMethod("AddInstanceUnTyped");

        /// <summary>
        /// The get instance method.
        /// </summary>
        private readonly MethodInfo _getInstanceMethod = typeof(PerfCounterManager).GetProperty("Instance").GetGetMethod();

        /// <summary>
        /// The get unique string method info.
        /// </summary>
        private readonly MethodInfo _getUniqueStringMethodInfo = typeof(RandomHelper).GetMethod("GenerateUniqueString");

        /// <summary>
        /// The name method info.
        /// </summary>
        private readonly MethodInfo _nameMethodInfo = typeof(IUnique).GetProperty("Id").GetGetMethod();

        #endregion Fields

        #region Constructors and Finalizers

        /// <summary>
        /// Prevents a default instance of the <see cref="PropertyPerfCounterInjector"/> class from being created.
        /// </summary>
        private PropertyPerfCounterInjector()
            : base(PropertyPerfCounterFilterMethodsStrategy.Instance)
        {
            this.CounterStorageType = CounterStorageType.Memory;
        }

        #endregion Constructors and Finalizers

        #region Public Properties

        /// <summary>
        /// Gets the instance.
        /// </summary>
        public static PropertyPerfCounterInjector Instance
        {
            get
            {
                return InstanceObject;
            }
        }

        /// <summary>
        /// Gets or sets the counter storage type.
        /// </summary>
        public CounterStorageType CounterStorageType { get; set; }

        #endregion Public Properties

        #region Methods

        /// <summary>
        /// The build dynamic method.
        /// </summary>
        /// <param name="methodInfo">
        /// The method info.
        /// </param>
        /// <returns>
        /// The <see cref="DynamicMethod"/>.
        /// </returns>
        protected override DynamicMethod BuildDynamicMethod(InjectableMethodInfo<PerfCounterCategoryAttribute> methodInfo)
        {
            Guid guid = Guid.NewGuid();
            string newMethodName = methodInfo.MethodName + "_" + guid.ToString().Replace("-", string.Empty);
            var dynamicMethod = new DynamicMethod(newMethodName, methodInfo.Method.ReturnType, new[] { methodInfo.Method.DeclaringType }, methodInfo.Method.DeclaringType);

            // AssemblyName aName = new AssemblyName("DynamicAssemblyExample");
            // System.Reflection.Emit.AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(aName, AssemblyBuilderAccess.RunAndSave);
            // ModuleBuilder mb = ab.DefineDynamicModule(aName.Name, aName.Name + ".dll");

            // TypeBuilder tb = mb.DefineType("MyDynamicType", TypeAttributes.Public);
            // MethodAttributes getSetAttr = MethodAttributes.Public |
            // MethodAttributes.SpecialName | MethodAttributes.Static;
            // MethodBuilder mbNumberSetAccessor = tb.DefineMethod(newMethodName, getSetAttr, methodInfo.ReturnType, new Type[]{methodInfo.DeclaringType});

            // ILGenerator ilGen = mbNumberSetAccessor.GetILGenerator();
            ILGenerator ilGen = dynamicMethod.GetILGenerator();

            ilGen.DeclareLocal(typeof(Exception));
            Label exBlock = ilGen.BeginExceptionBlock();

            ilGen.Emit(OpCodes.Ldarg, 0);

            ilGen.Emit(OpCodes.Callvirt, methodInfo.Method);
            ilGen.Emit(OpCodes.Leave, exBlock);

            ilGen.BeginCatchBlock(typeof(Exception));
            ilGen.Emit(OpCodes.Stloc, 0);
            ilGen.Emit(OpCodes.Rethrow);

            ilGen.BeginFinallyBlock();

            ilGen.Emit(OpCodes.Call, this._getInstanceMethod);

            ilGen.Emit(OpCodes.Ldarg_0);

            if (typeof(IUnique).IsAssignableFrom(methodInfo.Method.DeclaringType))
            {
                ilGen.Emit(OpCodes.Ldarg_0);
                ilGen.Emit(OpCodes.Callvirt, this._nameMethodInfo);
            }
            else
            {
                ilGen.Emit(OpCodes.Call, this._getUniqueStringMethodInfo);
            }

            ilGen.Emit(OpCodes.Ldc_I4, (int)this.CounterStorageType);
            ilGen.Emit(OpCodes.Callvirt, this._addInstanceUnTypedMethodInfo);
            ilGen.EndExceptionBlock();

            ilGen.Emit(OpCodes.Ret);

            // Type t = tb.CreateType();
            // ab.Save(aName.Name + ".dll");
            return dynamicMethod;
        }

        #endregion Methods
    }
}